﻿
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mail;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;

using CodeReviewApplication.Models;
using CodeReviewApplication.Models.Client;

namespace CodeReviewApplication.Controllers
{

    [HandleError]
    public class AccountController : Controller
    {
        private ICodeReviewRepository m_repository = new TransactedCodeReviewRepository(
            new SqlCodeReviewRepository()
            );

        public AccountController()
            : this(null, null)
        {
        }

        public AccountController(
            IFormsAuthentication formsAuth,
            IMembershipService service
            )
        {
            FormsAuth = formsAuth ?? new FormsAuthenticationService();
            MembershipService = service ?? new AccountMembershipService();
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        public IMembershipService MembershipService
        {
            get;
            private set;
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Index()
        {
            return View("Index");
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult LogOn()
        {
            return View("LogOn");
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Design",
            "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as Controller.Redirect()"
            )]
        public ActionResult LogOn(
            string userName,
            string password,
            bool rememberMe,
            string returnUrl
            )
        {
            if (!ValidateLogOn(userName, password))
            {
                return View("LogOn");
            }

            FormsAuth.SignIn(userName, rememberMe);
            
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult LogOff()
        {

            FormsAuth.SignOut();

            return RedirectToAction("Index", "Home");
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Register()
        {
            if (ApplicationConfiguration.Settings.CoreConfig.RegistrationRequest)
            {
                return RedirectToAction("RegistrationRequestNew");
            }

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View("Register");
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Register(
            string userName,
            string email,
            string password,
            string confirmPassword
            )
        {
            if (ApplicationConfiguration.Settings.CoreConfig.RegistrationRequest)
            {
                return RedirectToAction("RegistrationRequestNew");
            }

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            if (ValidateRegistration(userName, email, password, confirmPassword))
            {
                MembershipCreateStatus createStatus = MembershipService.CreateUser(
                    userName,
                    password,
                    email
                    );

                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsAuth.SignIn(userName, false);
                    
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError(
                        "_FORM",
                        ErrorCodeToString(createStatus)
                        );
                }
            }

            return View("Register");
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult RegisterRequest(Guid requestId)
        {
            if (!ApplicationConfiguration.Settings.CoreConfig.RegistrationRequest)
            {
                return Register();
            }

            try
            {
                RegistrationRequest request = m_repository.GetRegistrationRequest(
                    requestId
                    );

                if (request.StatusEnum != RegistrationRequestStatusEnum.Approved)
                {
                    throw new Exception(
                        string.Format(
                            "Registration request is not approved. Status: {0}",
                            request.StatusEnum.ToString()
                            )
                        );
                }

                if (Membership.GetUserNameByEmail(request.Email) == null)
                {
                    ViewData["Email"] = request.Email;
                }

                ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

                return View("Register");
            }
            catch(Exception exception)
            {
                System.Diagnostics.Trace.TraceError(exception.ToString());

                ModelState.AddModelError(
                    "none",
                    "Invalid Registration Request"
                    );

                return RedirectToAction("RegistrationRequestNew");
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult RegisterRequest(
            Guid requestId,
            string userName,
            string email,
            string password,
            string confirmPassword
            )
        {
            if (!ApplicationConfiguration.Settings.CoreConfig.RegistrationRequest)
            {
                return Register(userName, email, password, confirmPassword);
            }

            try
            {
                RegistrationRequest request = m_repository.GetRegistrationRequest(
                    requestId
                    );

                if (request.StatusEnum != RegistrationRequestStatusEnum.Approved)
                {
                    throw new Exception(
                        string.Format(
                            "Registration request is not approved. Status: {0}",
                            request.StatusEnum.ToString()
                            )
                        );
                }

                ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

                if (ValidateRegistration(userName, email, password, confirmPassword))
                {
                    MembershipCreateStatus createStatus = MembershipService.CreateUser(
                        userName,
                        password,
                        email
                        );

                    if (createStatus == MembershipCreateStatus.Success)
                    {
                        FormsAuth.SignIn(userName, false);

                        m_repository.Delete(request);
                        
                        return RedirectToAction("Index", "Home");
                    }
                    else
                    {
                        ModelState.AddModelError(
                            "_FORM",
                            ErrorCodeToString(createStatus)
                            );
                    }
                }

                return View("Register");
            }
            catch (Exception exception)
            {
                System.Diagnostics.Trace.TraceError(exception.ToString());

                ModelState.AddModelError(
                    "none",
                    "Invalid Registration Request"
                    );

                return RedirectToAction("RegistrationRequestNew");
            }
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult ChangePassword()
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View("ChangePassword");
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Design",
            "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Exceptions result in password not being changed."
            )]
        public ActionResult ChangePassword(
            string currentPassword,
            string newPassword,
            string confirmPassword
            )
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword))
            {
                return View("ChangePassword");
            }

            try
            {
                if (MembershipService.ChangePassword(
                    User.Identity.Name,
                    currentPassword,
                    newPassword))
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                else
                {
                    ModelState.AddModelError(
                        "_FORM",
                        "The current password is incorrect or the new password is invalid."
                        );
                    
                    return View("ChangePassword");
                }
            }
            catch
            {
                ModelState.AddModelError(
                    "_FORM",
                    "The current password is incorrect or the new password is invalid."
                    );
                
                return View("ChangePassword");
            }
        }


        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult ResetPasswordRequest()
        {
            return View("ResetPasswordRequest");
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Exceptions result in password not being changed."
            )]
        public ActionResult ResetPasswordRequest(string email)
        {
            if (!EmailValidator.Validate(email))
            {
                ModelState.AddModelError("email", "You must specify an email address.");

                return View("ResetPasswordRequest");
            }

            try
            {
                AccountPasswordResetModel resetModel = new AccountPasswordResetModel(
                    Membership.GetUserNameByEmail(email),
                    email
                    );

                if (!string.IsNullOrEmpty(resetModel.Name) && 
                    EmailValidator.Validate(resetModel.Email))
                {
                    string body = this.RenderViewToString(
                        "ResetPasswordRequestEmail",
                        resetModel
                        );

                    m_repository.Add(
                        new PasswordResetRequest(
                            resetModel.RequestId,
                            resetModel.Name
                            )
                        );

                    SmtpClient client = new SmtpClient();

                    MailMessage message = new MailMessage(
                        new MailMessage().From.ToString(),
                        resetModel.Email,
                        resetModel.Subject,
                        body
                        );

                    message.IsBodyHtml = true;
                    client.EnableSsl = true;

                    client.SendAsync(message, null);
                }
            }
            catch
            {
                //
                // NB: for security reason ignore exceptions silently
                // without informing users
                //
            }

            return View("ResetPasswordRequestSuccess");
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult ResetPassword(Guid requestId)
        {
            try
            {
                if (ValidateResetPassword(requestId))
                {
                    ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

                    return View("ResetPassword");
                }

                return RedirectToAction("Index");
            }
            catch
            {
                return RedirectToAction("Index");
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult ResetPassword(
            Guid requestId,
            string newPassword,
            string confirmPassword
            )
        {
            if (!ValidateResetPassword(requestId, newPassword, confirmPassword))
            {
                ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

                return View("ResetPassword");
            }

            try
            {
                PasswordResetRequest request = m_repository.Get(requestId);

                try
                {
                    MembershipUser user = Membership.GetUser(request.User);

                    if (user == null)
                    {
                        throw new Exception("Invalid reset request");
                    }

                    user.ChangePassword(user.ResetPassword(), newPassword);

                    return View("ChangePasswordSuccess");
                }
                finally
                {
                    m_repository.Delete(request);
                }
            }
            catch (Exception exception)
            {
                ModelState.AddModelError("_form", exception.Message);

                ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

                return View("ResetPassword");
            }
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult ChangePasswordSuccess()
        {
            return View("ChangePasswordSuccess");
        }

        protected override void OnActionExecuting(
            ActionExecutingContext filterContext
            )
        {
            if (filterContext.HttpContext.User.Identity is WindowsIdentity)
            {
                throw new InvalidOperationException(
                    "Windows authentication is not supported."
                    );
            }
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult RegistrationRequestNew()
        {
            return View(new NewRegistration());
        }

        [Authorize(Roles="Administrators")]
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult RegistrationRequests()
        {
            try
            {
                IEnumerable<RegistrationRequest> requests = 
                    m_repository.GetRegistrationRequests(User.Identity.Name);

                return new ObjectResult<RegistrationResponse>(
                    delegate() { return new RegistrationResponse(requests); },
                    delegate() { return View("RegistrationRequests", requests); }
                    );
            }
            catch (Exception exception)
            {
                return new ObjectResult<RegistrationResponse>(
                    delegate() { return new RegistrationResponse(exception); },
                    delegate()
                    {
                        return View(
                            "Error",
                            new HandleErrorInfo(
                                exception,
                                "Account",
                                "Registrations"
                                )
                            );
                    }
                    );
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [ObjectFilter(
            Param = "registration",
            RootType = typeof(NewRegistration),
            EnableForm=true
            )]
        public ActionResult RegistrationRequestCreate(
            NewRegistration registration
            )
        {
            try
            {
                if (!registration.Validate(ModelState))
                {
                    return View("RegistrationRequestNew", registration);
                }

                RegistrationRequest request = new RegistrationRequest(
                    registration
                    );
                
                m_repository.Add(request);

                return new ObjectResult<RegistrationResponse>(
                    delegate() { return new RegistrationResponse(request); },
                    delegate() { return View("RegistrationRequestSuccess"); }
                    );
            }
            catch (Exception exception)
            {
                return new ObjectResult<RegistrationResponse>(
                    delegate() { return new RegistrationResponse(exception); },
                    delegate()
                    {
                        return View(
                            "Error",
                            new HandleErrorInfo(
                                exception,
                                "Account",
                                "Registrations/New"
                                )
                            );
                    }
                    );
            }
        }

        [Authorize(Roles = "Administrators")]
        [AcceptVerbs(HttpVerbs.Put)]
        [ObjectFilter(
            Param = "registrations",
            RootType = typeof(EditRegistrations)
            )]
        public ActionResult RegistrationRequestsEdit(
            EditRegistrations registrations
            )
        {
            try
            {
                List<RegistrationRequest> updatedRequests = 
                        new List<RegistrationRequest>();

                foreach (RegistrationStatus status in registrations.Registrations)
                {
                    if (status.Status == RegistrationRequestStatusEnum.NotProcessed)
                    {
                        continue;
                    }

                    RegistrationRequest request = 
                        m_repository.GetRegistrationRequest(status.Id);

                    if (request.StatusEnum != status.Status)
                    {
                        request.StatusEnum = status.Status;

                        m_repository.Update(User.Identity.Name, request);
                        
                        updatedRequests.Add(request);
                    }
                }

                foreach (RegistrationRequest request in updatedRequests)
                {
                    if (request.StatusEnum == RegistrationRequestStatusEnum.Approved)
                    {
                        string body = this.RenderViewToString(
                            "RegistrationRequestApprovedEmail",
                            request
                            );

                        SmtpClient client = new SmtpClient();

                        MailMessage message = new MailMessage(
                            new MailMessage().From.ToString(),
                            request.Email,
                            "Code Review Application Registration",
                            body
                            );

                        try
                        {
                            message.IsBodyHtml = true;
                            client.EnableSsl = true;

                            client.SendAsync(message, null);
                        }
                        catch (Exception exception)
                        {
                            System.Diagnostics.Trace.TraceError(
                                exception.ToString()
                                );
                        }
                    }
                }

                return new ObjectResult<RegistrationResponse>(
                    delegate() { return new RegistrationResponse(updatedRequests); },
                    delegate() { return View("RegistrationRequests", updatedRequests); }
                    );
            }
            catch (Exception exception)
            {
                return new ObjectResult<RegistrationResponse>(
                    delegate() { return new RegistrationResponse(exception); },
                    delegate()
                    {
                        return View(
                            "Error",
                            new HandleErrorInfo(
                                exception,
                                "Account",
                                "Registrations/Edit"
                                )
                            );
                    }
                    );
            }
        }

        [Authorize(Roles = "Administrators")]
        [AcceptVerbs(HttpVerbs.Delete)]
        [ObjectFilter(
            Param = "registrations",
            RootType = typeof(DeleteRegistrations)
            )]
        public ActionResult RegistrationRequestsDelete(
            DeleteRegistrations registrations
            )
        {
            try
            {
                List<Guid> deletedIds = new List<Guid>();

                foreach (Guid id in registrations.Ids)
                {
                    m_repository.Delete(
                        m_repository.GetRegistrationRequest(id)
                        );

                    deletedIds.Add(id);
                }

                return new ObjectResult<RegistrationIdsResponse>(
                    delegate() { return new RegistrationIdsResponse(deletedIds); },
                    delegate() { return RegistrationRequests(); }
                    );
            }
            catch (Exception exception)
            {
                return new ObjectResult<RegistrationIdsResponse>(
                    delegate() { return new RegistrationIdsResponse(exception); },
                    delegate()
                    {
                        return View(
                            "Error",
                            new HandleErrorInfo(
                                exception,
                                "Account",
                                "Registrations/Delete"
                                )
                            );
                    }
                    );
            }
        }

        [Authorize(Roles = "Administrators")]
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult NotifyUsers()
        {
            return View("NotifyUsers", new UserNotification());
        }

        [Authorize(Roles = "Administrators")]
        [AcceptVerbs(HttpVerbs.Post)]
        [ObjectFilter(
            Param = "notification",
            RootType = typeof(UserNotification)
            )]
        public ActionResult NotifyUsers(
            UserNotification notification
            )
        {
            if (!notification.Validate(ModelState))
            {
                return new ObjectResult<UserNotificationReponse>(
                    delegate() 
                    { 
                        return new UserNotificationReponse(ModelState); 
                    },
                    delegate() 
                    {
                        return View(
                            "NotifyUsers",
                            new UserNotification(notification.Message)
                            );
                    }
                    );
            }

            try
            {
                MailMessage message = new MailMessage();

                message.Subject = "Code Review Application Notification";
                message.IsBodyHtml = true;
                message.Body = notification.Message;
                message.BodyEncoding = UTF8Encoding.UTF8;

                message.From = new MailMessage().From;

                foreach (MembershipUser user in Membership.GetAllUsers())
                {
                    if (!string.IsNullOrEmpty(user.Email))
                    {
                        message.Bcc.Add(new MailAddress(user.Email));
                    }
                }

                SmtpClient client = new SmtpClient();

                client.EnableSsl = true;

                client.Send(message);

                return new ObjectResult<UserNotificationReponse>(
                    delegate() { return new UserNotificationReponse(); },
                    delegate() { return View("NotifyUsersSuccess"); }
                    );
            }
            catch (Exception exception)
            {
                return new ObjectResult<UserNotificationReponse>(
                    delegate() { return new UserNotificationReponse(exception); },
                    delegate()
                    {
                        return View(
                            "Error",
                            new HandleErrorInfo(
                                exception,
                                "Account",
                                "UserNotify"
                                )
                            );
                    }
                    );
            }
        }

        #region Validation Methods

        private bool ValidateChangePassword(
            string newPassword,
            string confirmPassword
            )
        {
            if (
                (newPassword == null) || 
                (newPassword.Length < MembershipService.MinPasswordLength)
                )
            {
                ModelState.AddModelError(
                    "newPassword",
                    String.Format(
                        CultureInfo.CurrentCulture,
                        "You must specify a new password of {0} or more characters.",
                        MembershipService.MinPasswordLength
                        )
                    );
            }

            if (!String.Equals(
                newPassword,
                confirmPassword,
                StringComparison.Ordinal
                ))
            {
                ModelState.AddModelError(
                    "_FORM",
                    "The new password and confirmation password do not match."
                    );
            }

            return ModelState.IsValid;
        }

        private bool ValidateChangePassword(
            string currentPassword,
            string newPassword,
            string confirmPassword
            )
        {
            if (String.IsNullOrEmpty(currentPassword))
            {
                ModelState.AddModelError(
                    "currentPassword",
                    "You must specify a current password."
                    );
            }

            return ValidateChangePassword(newPassword, confirmPassword);
        }

        private bool ValidateResetPassword(Guid requestId)
        {
            try
            {
                PasswordResetRequest request = m_repository.Get(requestId);

                MembershipUser user = Membership.GetUser(request.User);

                if (user == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                ModelState.AddModelError(
                    "_form",
                    "Invalid password reset request"
                    );
            }

            return ModelState.IsValid;
        }

        private bool ValidateResetPassword(
            Guid requestId,
            string newPassword,
            string confirmPassword
            )
        {
            ValidateResetPassword(requestId);

            return ValidateChangePassword(newPassword, confirmPassword);
        }

        private bool ValidateLogOn(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError(
                    "username",
                    "You must specify a username."
                    );
            }
            if (String.IsNullOrEmpty(password))
            {
                ModelState.AddModelError(
                    "password",
                    "You must specify a password."
                    );
            }
            if (!MembershipService.ValidateUser(userName, password))
            {
                ModelState.AddModelError(
                    "_FORM",
                    "The username or password provided is incorrect."
                    );
            }

            return ModelState.IsValid;
        }

        private bool ValidateRegistration(
            string userName,
            string email,
            string password,
            string confirmPassword
            )
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError(
                    "username",
                    "You must specify a username."
                    );
            }
            
            if (!EmailValidator.Validate(email))
            {
                ModelState.AddModelError(
                    "email",
                    "You must specify a valid email address."
                    );
            }
            
            if (
                (password == null) || 
                (password.Length < MembershipService.MinPasswordLength)
                )
            {
                ModelState.AddModelError(
                    "password",
                    String.Format(
                        CultureInfo.CurrentCulture,
                        "You must specify a password of {0} or more characters.",
                        MembershipService.MinPasswordLength
                        )
                    );
            }
            
            if (!String.Equals(
                password,
                confirmPassword,
                StringComparison.Ordinal)
                )
            {
                ModelState.AddModelError(
                    "_FORM",
                    "The new password and confirmation password do not match."
                    );
            }
            
            return ModelState.IsValid;
        }

        private static string ErrorCodeToString(
            MembershipCreateStatus createStatus
            )
        {
            //
            // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Username already exists. Please enter a different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A username for that e-mail address already exists. Please enter a different e-mail address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                default:
                    return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
            }
        }
        #endregion
    }

    public interface IFormsAuthentication
    {
        void SignIn(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationService : IFormsAuthentication
    {
        public void SignIn(string userName, bool createPersistentCookie)
        {
            FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
        }

        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }

    public interface IMembershipService
    {
        int MinPasswordLength { get; }

        bool ValidateUser(string userName, string password);
        MembershipCreateStatus CreateUser(string userName, string password, string email);
        bool ChangePassword(string userName, string oldPassword, string newPassword);
    }

    public class AccountMembershipService : IMembershipService
    {
        private MembershipProvider m_provider;

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            m_provider = provider ?? Membership.Provider;
        }

        public int MinPasswordLength
        {
            get
            {
                return m_provider.MinRequiredPasswordLength;
            }
        }

        public bool ValidateUser(string userName, string password)
        {
            return m_provider.ValidateUser(userName, password);
        }

        public MembershipCreateStatus CreateUser(
            string userName,
            string password,
            string email
            )
        {
            MembershipCreateStatus status;

            m_provider.CreateUser(
                userName,
                password,
                email,
                null,
                null,
                true,
                null,
                out status
                );
            
            return status;
        }

        public bool ChangePassword(
            string userName,
            string oldPassword,
            string newPassword
            )
        {
            MembershipUser currentUser = m_provider.GetUser(
                userName,
                true
                );
            
            return currentUser.ChangePassword(oldPassword, newPassword);
        }
    }
}
